package org.azavea.otm.map;
import android.content.Context;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.JsonHttpResponseHandler;
import com.loopj.android.http.RequestParams;
import org.azavea.helpers.Logger;
import org.azavea.otm.data.InstanceInfo;
import org.json.JSONObject;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;
import java.util.Locale;
public class FallbackGeocoder {
// search box
private final InstanceInfo currentInstance;
private final LatLngBounds extent;
// activity context
private final Context context;
// the http client we are going to use to connect to google
private final AsyncHttpClient client;
// construct with a reference to the context and a search bounding box.
public FallbackGeocoder(Context context, InstanceInfo currentInstance) {
this.context = context;
this.currentInstance = currentInstance;
this.extent = currentInstance.getExtent();
client = new AsyncHttpClient();
}
// *Attempt* to use android's native reverse geocoder
//
// numerous failure conditions can cause this method to return null.
// best used in conjunction with the http geocoder as a fallback.
//
// due to a bug that surfaced in the android geocoder in 9/2014, this method
// was modified to stop using extents to hint/bias the geocoder to favor the
// region containing the instance. See this page for more details:
// http://stackoverflow.com/questions/25621087/android-geocoder-getfromlocationname-stopped-working-with-bounds
public LatLng androidGeocode(String addressText) {
List<Address> addresses;
Geocoder g = new Geocoder(this.context);
try {
addresses = g.getFromLocationName(addressText, 10);
} catch (IOException e) {
Logger.error("Geocoder exception", e);
return null;
}
double instanceRadius, instanceLat, instanceLng;
try {
instanceRadius = currentInstance.getRadius();
instanceLat = currentInstance.getLat();
instanceLng = currentInstance.getLon();
} catch (Exception e) {
Logger.error("Required instance data not found. Exiting android geocoder", e);
return null;
}
// addresses returned by the geocoder are sorted by match quality
// return the first one that is within an acceptable distance
for (Address address : addresses) {
float[] results = new float[1];
Location.distanceBetween(instanceLat, instanceLng, address.getLatitude(), address.getLongitude(), results);
if (results[0] <= instanceRadius) {
return new LatLng(address.getLatitude(), address.getLongitude());
}
}
return null;
}
// Use Google's Http geocoder.
public void httpGeocode(String address, JsonHttpResponseHandler handler) {
String urlFormat = "http://maps.googleapis.com/maps/api/geocode/json?sensor=false&address=%s";
String url;
if (extent != null) {
// Use this format to compose a url for google's geocode
urlFormat += "&bounds=%f,%f%%7c%f,%f";
// interpolate address and bounds into that format string
url = String.format(Locale.US, urlFormat, URLEncoder.encode(address), extent.southwest.latitude,
extent.southwest.longitude, extent.northeast.latitude, extent.northeast.longitude);
} else {
url = String.format(Locale.US, urlFormat, URLEncoder.encode(address));
}
// execute the http request.
client.get(url, new RequestParams(), handler);
}
// adapt a google JSON response from httpGeocode above into a
// LatLng object.
public static LatLng decodeGoogleJsonResponse(JSONObject json) {
try {
// parse out the lat/long from the first entry in the results
// array.
JSONObject location = json.getJSONArray("results").getJSONObject(0).getJSONObject("geometry")
.getJSONObject("location");
double lat = location.getDouble("lat");
double lon = location.getDouble("lng");
// If any of the above triggers an exception, or if we are null by
// the
// time we get to here, just return null.
return new LatLng(lat, lon);
} catch (Exception e) {
Logger.error("Problem parsing geocoder response", e);
return null;
}
}
}